package JavaEE;
// 单例模式
// 期望这个类只能有一个实例

// 饿汉模式,代表:比较紧迫,(类加载的时候,就将这个实例初始化出来)
// "线程安全,不需要加锁"
class Singleton{
    // 把这个唯一的实例创建出来(直接创建,饿汉模式)
    private static Singleton instance = new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
    // 构造方法设置成private,避免在类的外部new(会导致不止有一个实例,不是单例模式了)
    private Singleton(){};
}
// 懒汉模式,get时才会初始化唯一实例,表示不紧迫,比较懒~
// "线程不安全!需要加锁处理!"
class SingletonLazy{
    // 创建唯一实例,初始化为null,等待后续get时初始化.
    // 一定要加volatile的原因:
    // 我们可以将创建instance,粗略的分为三步:
    // 1.申请内存空间 2.在内存空间上进行初始化(构造方法) 3.内存地址,保存到引用变量中
    // 那么可能会触发"编译器优化"(可能被重排序为1,3,2),那么又由于"调度执行的随机性":
    // 可能t1要创建instance,执行了1,3步后,紧接着t2也跑起来了,而经过1,3步,instance已经存在了(不再是null)
    // 那么,直接进不去第一个if语句,导致直接返回instance(非空),就可能引发不可预知的后果!
    private static volatile SingletonLazy instance = null;
    private static Object locker = new Object();
    public static SingletonLazy getInstance(){
        // 此处用于判定"instance是否第一次get"
        // 如果不是第一次,则直接return(防止多次创建锁的开销)
        if(instance == null) {
            // 通过加锁,将"instance==null和初始化"变成"原子的"
            // but!实际上第一次get后,后续每次get到的都是第一次初始化的instance
            // 当instance!=null时,加锁的操作就是多余的操作,而同时加锁的"代价"又比较大,所以需要避免多次加锁!
            synchronized (locker) {
                // 这里如果不加锁,可能由于"调度的随机性"导致多个线程同时判定instance为null
                // 从而可能初始化多个instance实例,从而也就打破了"单例模式"的规则了~
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
    private SingletonLazy(){}
}
public class SingletonTest {
    public static void main(String[] args) {

        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);//true

        SingletonLazy s3 = SingletonLazy.getInstance();
        SingletonLazy s4 = SingletonLazy.getInstance();
        System.out.println(s3 == s4);//true

    }
}
